/**************************************************************************
 * Copyright (C) 2011 Tyler Robertson
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************/


#include "Arduino.h"
#include "Wifly/WiflyDevice.h"
#include "SPI/SerialPeripheralInterface.h"
#include "Common/crc16.h"

// SC16IS740 Register Definitions
#define IER        (0x01 << 3)
#define FCR        (0x02 << 3)
#define IIR        (0x02 << 3)
#define LCR        (0x03 << 3)
#define MCR        (0x04 << 3)
#define LSR        (0x05 << 3)
#define MSR        (0x06 << 3)
#define SPR        (0x07 << 3)
#define TXFIFO     (0x08 << 3)
#define RXFIFO     (0x09 << 3)
#define DLAB       (0x80 << 3)
#define IODIR      (0x0A << 3)
#define IOSTATE    (0x0B << 3)
#define IOINTMSK   (0x0C << 3)
#define IOCTRL     (0x0E << 3)
#define EFCR       (0x0F << 3)

#define DLL        (0x00 << 3)
#define DLM        (0x01 << 3)
#define EFR        (0x02 << 3)
#define XON1       (0x04 << 3)  
#define XON2       (0x05 << 3)
#define XOFF1      (0x06 << 3)
#define XOFF2      (0x07 << 3)

#define BRIDGE_CS_PIN 9

static char message_buffer[ 128 ];


// Functions to read and write to the sc16is740 spi<->uart bridge
static void write_to_bridge_register( unsigned char reg, unsigned char value )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( reg );
	spi_write_byte( value );
	spi_deselect_slave( BRIDGE_CS_PIN );
}

static unsigned char read_from_bridge_register( unsigned char reg )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( reg | 0x80 );
	unsigned char data =  spi_read_byte();
	spi_deselect_slave( BRIDGE_CS_PIN );
	return data;
}


void wifly_initialize()
{
	Serial.println( "Initializing wifly device..." );
	
	// Set CS Pin as output
	pinMode( BRIDGE_CS_PIN, OUTPUT );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	
	// Initialize the SC16IS740 Bridge
	spi_select_slave( BRIDGE_CS_PIN );
	write_to_bridge_register( LCR, 0x80 ); // Set mode to "enable divisor latch"
	write_to_bridge_register( DLL, 0x50 ); // Program least significant divisor
	write_to_bridge_register( DLM, 0x00 ); // Program most significant divisor
	write_to_bridge_register( LCR, 0xBF );
	write_to_bridge_register( EFR, 0x10 );
	write_to_bridge_register( LCR, 0x03 ); // Set to 8 bit word length
	write_to_bridge_register( FCR, 0x06 ); // Clear and reset TX & RX registers
	write_to_bridge_register( FCR, 0x01 ); // Enable FIFO mode
	
	// Test
	write_to_bridge_register( SPR, 'H' );
	char received_byte = read_from_bridge_register( SPR );
	if( received_byte == 'H' )
	{
		Serial.println( "SPI<->UART bridge initialized!" );
	}
	else
	{
		Serial.println( "Could not initialize SPI<->UART bridge!!" );
	}
	
	spi_deselect_slave( BRIDGE_CS_PIN );
}

unsigned char wifly_receive_byte()
{
	return read_from_bridge_register( 0 );
}

void wifly_send_byte( unsigned char data )
{
	write_to_bridge_register( 0, data );
}

void wifly_send_data( const unsigned char *data, unsigned int size_in_bytes )
{
	// Transmit Holding Register (actually a FIFO) can only hold 64 bytes 
	int i=0;
	for( ; i<size_in_bytes/64; ++i )
	{
		spi_select_slave( BRIDGE_CS_PIN );
		spi_write_byte( 0 );
		spi_write_array( data + i*64, 64 );
		spi_deselect_slave( BRIDGE_CS_PIN );
	}
	
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_array( data + i*64, size_in_bytes%64 );
	spi_deselect_slave( BRIDGE_CS_PIN );	
}

void wifly_receive_data( unsigned char *data, unsigned int size_in_bytes )
{
	// Receiving Holding Register can only hold 64 bytes
	int i=0;
	for( ; i<size_in_bytes/64; ++i )
	{
		spi_select_slave( BRIDGE_CS_PIN );
		spi_write_byte( 0 );
		spi_read_array( data + i*64, 64 );
		spi_deselect_slave( BRIDGE_CS_PIN );
	}

	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_read_array( data + i*64, size_in_bytes%64 );
	spi_deselect_slave( BRIDGE_CS_PIN );	
}


void wifly_send_ascii_packet( const char *fmt, ... )
{
	unsigned num_characters_written = 0;
	
	va_list args;
	va_start( args, fmt );
	num_characters_written = vsprintf( message_buffer, fmt, args );
	va_end( args );
	
	ascii_packet_t ascii_packet;
	ascii_packet.message = message_buffer;
	ascii_packet.num_characters = num_characters_written;
	wifly_send_packet( &ascii_packet );
}

bool wifly_has_data_available()
{
	return ( read_from_bridge_register( LSR ) & 0x01 != 0 );
}

unsigned char wifly_get_num_bytes_available()
{
	return read_from_bridge_register( RXFIFO );
}

void wifly_send_packet( const ascii_packet_t *ascii_packet )
{
	// Neede to manage because we're going through the SPI<->UART Bridge 
	// Which can only hold 64 bytes
	
	unsigned short computed_crc = crc16( 0, (const unsigned char*)ascii_packet->message, ascii_packet->num_characters );
	
	// Transmit the number of characters first
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_byte( PT_ASCII_PACKET );
	spi_write_array( (const unsigned char*)&ascii_packet->num_characters, 4 );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	// Safely transmt the message without overflow
	wifly_send_data( (const unsigned char*)ascii_packet->message, ascii_packet->num_characters );
	
	// Now transmit the crc & end of packet
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_array( (const unsigned char*)&computed_crc, 2 );
	spi_write_byte( PT_END_OF_PACKET );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	// Whew, we're done now
}

void wifly_send_packet( const statistics_packet_t *statistics_packet )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_packet( statistics_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
}

void wifly_send_packet( const timer_op_packet_t *timer_op_packet )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_packet( timer_op_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
}

void wifly_send_packet( const command_packet_t *command_packet )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );	
	spi_write_packet( command_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
}

void wifly_send_packet( const state_packet_t *state_packet )
{
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0 );
	spi_write_packet( state_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
}


/*
bool wifly_receive_packet( ascii_packet_t *ascii_packet )
{
	bool succeeded = false;
	
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0x80 );
	succeeded = spi_read_packet( ascii_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	return succeeded;
}
 */

bool wifly_receive_packet( statistics_packet_t *statistics_packet )
{
	bool succeeded = false;
	unsigned long startTime = 0;
	unsigned long now = 0;
	
	startTime = millis();
	
	// Wait to ensure enough data is availabe, bail if waiting too long
	while( wifly_get_num_bytes_available() < sizeof( statistics_packet_t ) + 3 )
	{
		now = millis();
		if( now - startTime > 20 ) // 20 ms timeout
			return false;
		
		startTime = now;
	}
	
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0x80 );
	succeeded = spi_read_packet( statistics_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	return succeeded;
}

bool wifly_receive_packet( timer_op_packet_t *timer_op_packet )
{
	bool succeeded = false;
	unsigned long startTime = 0;
	unsigned long now = 0;
	
	startTime = millis();
	
	// Wait to ensure enough data is availabe, bail if waiting too long
	while( wifly_get_num_bytes_available() < sizeof( timer_op_packet_t ) + 3 )
	{
		now = millis();
		if( now - startTime > 20 ) // 20 ms timeout
			return false;
		
		startTime = now;
	}
	
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0x80 );
	succeeded = spi_read_packet( timer_op_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	return succeeded;
}

bool wifly_receive_packet( command_packet_t *command_packet )
{
	bool succeeded = false;
	unsigned long startTime = 0;
	unsigned long now = 0;
	
	startTime = millis();
	
	// Wait to ensure enough data is availabe, bail if waiting too long
	while( wifly_get_num_bytes_available() < sizeof( command_packet_t ) + 3 )
	{
		now = millis();
		if( now - startTime > 20 ) // 20 ms timeout
			return false;
		
		startTime = now;
	}
	
	// Alright, let's do this
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0x80 );
	succeeded = spi_read_packet( command_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );	
	
	return succeeded;
}

bool wifly_receive_packet( state_packet_t *state_packet )
{
	bool succeeded = false;
	unsigned long startTime = 0;
	unsigned long now = 0;
	
	startTime = millis();
	
	// Wait to ensure enough data is availabe, bail if waiting too long
	while( wifly_get_num_bytes_available() < sizeof( state_packet_t ) + 3 )
	{
		now = millis();
		if( now - startTime > 20 ) // 20 ms timeout
			return false;
		
		startTime = now;
	}
	
	spi_select_slave( BRIDGE_CS_PIN );
	spi_write_byte( 0x80 );
	succeeded = spi_read_packet( state_packet );
	spi_deselect_slave( BRIDGE_CS_PIN );
	
	return succeeded;
}
